package smartlifev30.com.sdk.api.gateway_app;

import android.util.Log;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import smartlifev30.com.sdk.bean.request.SearchGateWayRequest;
import smartlifev30.com.sdk.bean.response.GatewayInfo;
import smartlifev30.com.sdk.bean.response.SearchGatewayResponse;
import smartlifev30.com.sdk.net.baiwei.protocol.Header;
import smartlifev30.com.sdk.util.JsonUtil;
import smartlifev30.com.sdk.util.MsgIdBuilder;

/**
 * @author uesr
 * @date 2018/7/10
 * 发现网关
 * SearchGateway searchGateWay = new SearchGateway();
 * searchGateWay.startSearch(new SearchGatewayCallBack....);
 * ........
 * searchGateWay.stopSearch();
 */
public class SearchGateway {

    private static final String TAG = "SearchGateway";

    /**
     * 广播发送端口
     */
    private final int BROADCAST_SEND_PORT = 7103;

    /**
     * 广播接收端口
     */
    private final int BROADCAST_RECEIVE_PORT = 7102;

    /**
     * 广播的IP地址
     */
    private final String BROADCAST_IP = "255.255.255.255";

    private DatagramSocket mDatagramSendSocket;
    private DatagramSocket mDatagramReceiveSocket;
    private InetAddress mIneptAddress;
    private volatile boolean isRunning;
    private ReceiveThread receiveThread;
    private SendThread sendThread;
    private SearchGatewayCallBack callBack;
    private List<GatewayInfo> gatewayInfo = new ArrayList<>();

    public SearchGateway() {
        init();
    }

    /**
     * 初始化 创建socket
     */
    private void init() {
        try {
            mDatagramSendSocket = new DatagramSocket(null);
            mDatagramSendSocket.setReuseAddress(true);
            mDatagramSendSocket.bind(new InetSocketAddress(BROADCAST_SEND_PORT));
            mDatagramReceiveSocket = new DatagramSocket(null);
            mDatagramReceiveSocket.setReuseAddress(true);
            mDatagramReceiveSocket.bind(new InetSocketAddress(BROADCAST_RECEIVE_PORT));
            mIneptAddress = InetAddress.getByName(BROADCAST_IP);
        } catch (SocketException e) {
            if (callBack != null) {
                callBack.failed(e, "created datagramSocket exception");
            }
            stopSearch();
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }

    /**
     * 开始搜索(发现)网关。调用该方法后，将会开启发送和接受广播线程。
     * 直到调用{@link SearchGateway#stopSearch}后停止。
     *
     * @param callBack
     */
    public void startSearch(SearchGatewayCallBack callBack) {
        if (isRunning) {
            return;
        }
        isRunning = true;
        this.callBack = callBack;
        receiveThread = new ReceiveThread("search gateway receiveThread ");
        sendThread = new SendThread("search gateway sendThread ");
        receiveThread.start();
        sendThread.start();
    }

    /**
     * 停止发送，接收线程，释放socket资源
     */
    public void stopSearch() {
        isRunning = false;
        receiveThread.interrupt();
        sendThread.interrupt();
        free();
    }

    /**
     * 发送广播线程每隔2s发送一次，发现网关的广播消息。在{@link SearchGateway#stopSearch()}后停止发送广播。
     * 1.只发送一次消息，网关可能不会回复消息
     * 2.只发送一次可能收到的网关信息不全。 2.1）其他设备正在发生次广播。 2.2）新网关接入
     */
    private class SendThread extends Thread {

        SendThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            SearchGateWayRequest searchGateWayReq = new SearchGateWayRequest();
            while (isRunning) {
                searchGateWayReq.setApi_version("0.1");
                searchGateWayReq.setTo("");
                searchGateWayReq.setFrom("");
                searchGateWayReq.setMsg_id(MsgIdBuilder.build());
                searchGateWayReq.setMsg_class("gateway_mgmt");
                searchGateWayReq.setMsg_name("gateway_discovery");
                searchGateWayReq.setMsg_type("get");
                try {
                    String cmd = Header.add(JsonUtil.toString(searchGateWayReq));
                    sendMsg(cmd.getBytes());
                    Log.d(TAG, "发送消息:" + cmd);
                    Thread.sleep(5000);
                } catch (IOException e) {
                    if (!isRunning) {
                        //手动停止 中断线程
                    } else {
                        if (callBack != null) {
                            callBack.failed(e, getName() + " _ " + e.toString());
                        }
                        stopSearch();
                    }
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        private void sendMsg(byte[] b) throws IOException {
            DatagramPacket mDatagramPacket = new DatagramPacket(b, b.length, mIneptAddress, BROADCAST_SEND_PORT);
            if (mDatagramSendSocket != null) {
                mDatagramSendSocket.send(mDatagramPacket);
            }
        }
    }

    /**
     * 接受广播线程，一直读取广播消息，并将接受到的消息通知回调。在{@link SearchGateway#stopSearch()}后停止接受广播。
     */
    private class ReceiveThread extends Thread {

        ReceiveThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            while (isRunning) {
                try {
                    byte[] msg = receive();
                    parseMsg(msg);
                } catch (IOException e) {
                    if (callBack != null) {
                        if (!isRunning) {
                            //手动停止搜索 中断线程
                        } else {
                            callBack.failed(e, getName() + " _ " + e.toString());
                            stopSearch();
                        }
                    }
                    e.printStackTrace();
                }
            }
        }

        private byte[] receive() throws IOException {
            byte[] buffer = new byte[1024];
            DatagramPacket mdp = new DatagramPacket(buffer, buffer.length);
            Log.d(TAG, "接收数据......");
            if (mDatagramReceiveSocket != null) {
                mDatagramReceiveSocket.receive(mdp);
            }
            return buffer;
        }

        private void parseMsg(byte[] msg) {
            if (!Header.check(msg)) {
                Log.e("0-0-", "协议头错误");
            }
            int contentLength = Header.getContentLength(msg);
            String s = new String(msg, Header.HEAD_LENGTH, contentLength);
            SearchGatewayResponse gatewayInfo = JsonUtil.toObject(s, SearchGatewayResponse.class);
            if (gatewayInfo != null) {
                int status = gatewayInfo.getStatus();
                if (status == 0) {
                    GatewayInfo gateway = gatewayInfo.getGateway();
                    if (!SearchGateway.this.gatewayInfo.contains(gateway)) {
                        SearchGateway.this.gatewayInfo.add(gateway);
                        if (callBack != null) {
                            Log.d(TAG, gateway.toString());
                            callBack.addressInfoCallback(gateway);
                        }
                    }
                } else {
                    Log.e(TAG, "search failed code:" + status);
                }
            }
        }
    }

    /**
     * 回收socket资源
     */
    private void free() {
        if (mDatagramSendSocket != null) {
            mDatagramSendSocket.disconnect();
            mDatagramSendSocket.close();
            mDatagramSendSocket = null;
        }
        if (mDatagramReceiveSocket != null) {
            mDatagramReceiveSocket.disconnect();
            mDatagramReceiveSocket.close();
            mDatagramReceiveSocket = null;
        }
    }

    /**
     * 搜索网关回掉接口
     */
    public interface SearchGatewayCallBack {

        /**
         * 发现网关后,网关信息回调。当搜索到有新增网关时，会触发此方法,可能会多次触发。
         *
         * @param gateways 新增网关
         */
        void addressInfoCallback(GatewayInfo gateways);

        /**
         * 其他IO异常。 比如读，发送广播IO异常, 创建socket异常
         *
         * @param e
         * @param message 描述信息
         */
        void failed(Throwable e, String message);
    }
}
